package main

import (
	"encoding/json"
	"flag"
	"fmt"
	log "github.com/sirupsen/logrus"
	"gopkg.in/natefinch/lumberjack.v2"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"
	"usbipproxy/application"
	"usbipproxy/common"
	"usbipproxy/domain/service"
	"usbipproxy/domain/tcpsession"
	"usbipproxy/domain/usbipclientsrv"
	"usbipproxy/pb"
)

var (
	srvCfg                 = &common.ServerConfig{}
	stopChan chan struct{} = make(chan struct{}, 1)
)

var (
	//listenHttpAddr  = flag.String("http_addr", "0.0.0.0:7777", "http监听主机")
	listenTcpAddr   = flag.String("tcp_addr", "0.0.0.0:7779", "tcp监听主机")
	logOutType      = flag.String("log_out_type", "file", "console:标准输出 file:写文件")
	logMaxFileNum   = flag.Int("log_max_file_num", 3, "保留旧日志文件数量")
	logFileName     = flag.String("log_file_log_name", "usbipclient.log", "日志文件名")
	logMaxFileSize  = flag.Int("log_max_file_size", 1024, "单个文件最大大小（1024M）")
	logMaxFileAge   = flag.Int("log_max_file_age", 7, "保留天数（days）")
	logFileCompress = flag.Bool("log_file_compress", true, "是否压缩旧日志")
	logLevel        = flag.String("log_level", "info", "日志等级")
	configFilepath  = flag.String("config_filepath", "usbipclient.config", "配置文件")
	daemonMode      = flag.Bool("d", false, "daemon")
)

// GOOS=linux GOARCH=arm64 go build
// GOOS=linux GOARCH=amd64 go build
// GOOS=windows GOARCH=amd64 go build
func main() {
	flag.Parse()
	if *daemonMode {
		daemonize()
	}
	//srvCfg.HttpServer = listenHttpAddr
	srvCfg.TcpServer = listenTcpAddr
	srvCfg.LogConfig = &common.LogConfig{
		OutType:      logOutType,
		FileName:     logFileName,
		MaxFileNum:   logMaxFileNum,
		MaxFileSize:  logMaxFileSize,
		MaxFileAge:   logMaxFileAge,
		FileCompress: logFileCompress,
		Level:        logLevel,
	}

	ex, err := os.Executable()
	if err != nil {
		panic(err)
	}
	// 获取可执行文件所在的目录
	exPath := filepath.Dir(ex)
	configFilepathStr := *configFilepath

	tmpDir := filepath.Dir(configFilepathStr)
	if tmpDir == "." || tmpDir == "./" {
		configFilepathStr = filepath.Join(exPath, configFilepathStr)
	}

	data, err := os.ReadFile(configFilepathStr)
	if len(data) > 0 {
		// 解析到结构体
		var tmpCfg common.ServerConfig
		if err := json.Unmarshal(data, &tmpCfg); err != nil {
			panic(fmt.Errorf("解析JSON失败: %w", err))
		}

		srvCfg.Merge(&tmpCfg)
	}
	log.SetFormatter(&log.JSONFormatter{
		// 设置时间格式
		TimestampFormat: "2006-01-02 15:04:05.000",
	})
	if strings.Contains(strings.ToLower(srvCfg.GetLogOutType()), "file") {
		ex, err := os.Executable()
		if err != nil {
			panic(err)
		}

		fileName := srvCfg.GetLogFileName()
		// 获取可执行文件所在的目录
		exPath := filepath.Dir(ex)

		idx := strings.Index(fileName, ".")
		if idx > 0 {
			fileName = fmt.Sprintf("%s_%d%s", fileName[:idx], (uint32)(time.Now().Unix()), fileName[idx:])
		} else {
			fileName = fmt.Sprintf("%s%d", fileName, (uint32)(time.Now().Unix()))
		}

		tmpDir := filepath.Dir(fileName)
		if tmpDir == "." || tmpDir == "./" {
			fileName = filepath.Join(exPath, fileName)
		}
		log.SetOutput(&lumberjack.Logger{
			Filename:   fileName,                    // 日志文件名
			MaxSize:    srvCfg.GetLogMaxFileSize(),  // 单个文件最大大小（1024）
			MaxBackups: srvCfg.GetLogMaxFileNum(),   // 保留旧日志文件数量
			MaxAge:     srvCfg.GetLogMaxFileAge(),   // 保留天数（days）
			Compress:   srvCfg.GetLogFileCompress(), // 是否压缩旧日志
		})

		go cleanupLogs("usbipclient", srvCfg.GetLogMaxFileAge())
	}
	usbIpType := pb.UsbIpType_USBIP_TYPE_CLIENT

	logLevelInt, err := log.ParseLevel(srvCfg.GetLogLevel())
	if err != nil {
		logLevelInt = log.TraceLevel
	}
	log.SetLevel(logLevelInt)
	log.Infof("usbclient start tcp_server:%s", srvCfg.GetTcpserverAddr())

	msgNotifySvc := service.NewMsgNotifyServiceImpl()
	clientCmdSvc := service.FactoryUsbIpClientCmd.Create()
	usbipServerMgr := usbipclientsrv.NewUsbipServerMgrServiceImpl(clientCmdSvc)
	tcpMsgHandleSvc := application.NewTcpMsgHostHandleService(msgNotifySvc, usbipServerMgr, service.NewSwDeviceWindowsServiceImpl(clientCmdSvc), clientCmdSvc)
	sessMgr := tcpsession.NewTcpSessionMgr()
	tcpServerSvc := application.NewTcpServerServiceImpl(srvCfg.GetTcpserverAddr(), usbIpType, msgNotifySvc, tcpMsgHandleSvc, sessMgr, service.NewSwDeviceWindowsServiceImpl(clientCmdSvc))
	tcpServerSvc.Start()

	signalChan := make(chan os.Signal, 1)
	signalList := []os.Signal{syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP, syscall.SIGQUIT, syscall.Signal(0xa)}
	signal.Notify(signalChan, signalList...)

	log.Infof("waiting signal pid:%v", os.Getpid())
	// 等待系统信号
	sig := <-signalChan
	close(stopChan)

	if usbipServerMgr != nil {
		usbipServerMgr.Stop()
	}
	time.Sleep(time.Second * 3)
	log.Infof("receive signal '%s'", sig)
}

func daemonize() {
	// 获取当前可执行文件路径
	execPath, err := os.Executable()
	if err != nil {
		log.Println("Error getting executable path:", err)
		os.Exit(1)
	}

	// 构建新参数列表（去掉-d标志）
	var newArgs []string
	for _, arg := range os.Args[1:] {
		if arg == "-d" || arg == "--d" {
			continue
		}
		newArgs = append(newArgs, arg)
	}

	// 创建守护进程
	cmd := exec.Command(execPath, newArgs...)
	//cmd.SysProcAttr = &syscall.SysProcAttr{
	//	Setsid: true, // 创建新会话组
	//}

	// 重定向标准IO到/dev/null
	nullDev, _ := os.OpenFile(os.DevNull, os.O_RDWR, 0)
	cmd.Stdout = nullDev
	cmd.Stderr = nullDev
	cmd.Stdin = nullDev

	// 启动守护进程
	if err := cmd.Start(); err != nil {
		log.Println("Error starting daemon:", err)
		os.Exit(1)
	}

	// 记录守护进程PID
	pid := cmd.Process.Pid
	log.Printf("Daemon started with PID: %d\n", pid)

	// 保存PID到文件
	//savePID(pid)

	// 父进程退出
	os.Exit(0)
}

func cleanupLogs(exeName string, day int) {
	// 设置定时器，每分钟执行一次
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()

	for {
		select {
		case <-stopChan:
			return
		case <-ticker.C:
			// 获取执行程序所在的目录
			exePath, err := os.Executable()
			if err != nil {
				log.Printf("获取执行路径失败: %v\n", err)
				continue
			}
			currentDir := filepath.Dir(exePath)

			// 读取目录中的文件
			files, err := ioutil.ReadDir(currentDir)
			if err != nil {
				log.Printf("读取目录失败: %v\n", err)
				continue
			}

			// 计算7天前的时间戳
			sevenDaysAgo := time.Now().AddDate(0, 0, -day).Unix()

			// 正则表达式匹配日志文件名
			regStr := fmt.Sprintf(`%v_(\d+)\.log`, exeName)
			pattern := regexp.MustCompile(regStr)

			// 遍历文件
			for _, file := range files {
				if file.IsDir() {
					continue
				}

				// 检查文件名是否符合模式
				matches := pattern.FindStringSubmatch(file.Name())
				if len(matches) < 2 {
					continue
				}

				// 提取时间戳
				timestampStr := matches[1]
				timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
				if err != nil {
					log.Infof("解析时间戳失败: %s, %v\n", file.Name(), err)
					continue
				}

				// 检查文件是否超过7天
				if timestamp < sevenDaysAgo {
					// 删除文件
					fullPath := filepath.Join(currentDir, file.Name())
					// 检查文件是否正在使用
					if common.IsFileInUse(fullPath) {
						log.Infof("[跳过] 文件正在使用中: %s\n", file.Name())
						continue
					}
					err := os.Remove(fullPath)
					if err != nil {
						log.Infof("删除文件失败: %s, %v\n", file.Name(), err)
					} else {
						log.Infof("已删除过期文件: %s\n", file.Name())
					}
				}
			}

			//log.Infof("清理完成于: %s\n", time.Now().Format("2006-01-02 15:04:05"))
		}
	}
}
